﻿using System.Collections.Generic;
using System.Text;
using CRUDGenerator.Sql;

namespace CRUDGenerator
{
    public class BLLGenerator
    {
        private readonly bool _isCreate;

        private readonly bool _isValidate;

        private readonly bool _isDelete;

        private readonly bool _isListing;

        private readonly bool _isSave;

        private readonly bool _isSelectById;

        private readonly IEnumerable<ColumnInformation> _columns;

        public BLLGenerator(IEnumerable<ColumnInformation> columns, bool isCreate= true, bool isSave = true, bool isDelete = true, bool isListing = true, bool isSelectById = true, bool isValidate = true)
        {
            _isCreate = isCreate;
            _isValidate = isValidate;
            _isDelete = isDelete;
            _isListing = isListing;
            _isSave = isSave;
            _isSelectById = isSelectById;
            _columns = columns;
        }

        public string GenerateFilter(string name)
        {
            var result = new StringBuilder();
            result.AppendLine("using System.ComponentModel.DataAnnotations;");
            result.AppendLine("namespace BLL.Services.", name,".Listing");
            result.AppendLine("{");
            result.AppendLine(1, "public class ", name, "Filter");
            result.AppendLine(1, "{");

            foreach (var column in _columns)
            {
                if (!column.IsIdentity && !column.IsPrimaryKey)
                {
                    result.AppendLine(2, "[Display(Name = \"", column.Name, "\")]");
                    result.AppendLine(2, "public string ", column.Name, " { get; set; }");
                    result.AppendLine();
                }
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        public string GenerateListing(string name)
        {
            var result = new StringBuilder();
            result.AppendLine("using System.Collections.Generic;");
            result.AppendLine("using System.ComponentModel.DataAnnotations;");
            result.AppendLine();
            result.AppendLine("namespace BLL.Services.", name, ".Listing");
            result.AppendLine("{");
            result.AppendLine(1, "public class ", name, "Listing");
            result.AppendLine(1, "{");
            result.AppendLine(2, "public class ", name, "Listing");
            result.AppendLine(2, "{");
            result.AppendLine(3, name, " = new List<", name, ">();");
            result.AppendLine(3, "Filter = new ", name, "Filter();");
            result.AppendLine(2, "}");
            result.AppendLine();
            result.AppendLine(2, "public List<", name, "> ", name, " { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public int Page { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public int TotalPages { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public int Size { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public int Direction { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public string Column { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public ", name, "Filter Filter { get; set; }");
            result.AppendLine();
            result.AppendLine(2, "public class ", name, " { get; set; }");
            result.AppendLine(2, "{");

            foreach (var column in _columns)
            {
                if (!column.IsIdentity && !column.IsPrimaryKey)
                {
                    result.AppendLine(3, "[Display(Name = \"", column.Name, "\")]");
                }

                result.AppendLine(3, "public ", GetCSharpType(column.DataTypeName, column.IsPrimaryKey), " ", column.Name, " { get; set; }");
                result.AppendLine();
            }

            result.AppendLine(2, "}");
            result.AppendLine(1, "}");
            result.AppendLine("}");

            return result.ToString();
        }

        public string GenerateDomen(string name)
        {
            var result = new StringBuilder();

            result.AppendLine("namespace BLL.Services.", name);
            result.AppendLine("{");
            result.AppendLine(1, "public class ", name);
            result.AppendLine(1, "{");

            foreach (var column in _columns)
            {
                result.AppendLine(2, "public ", GetCSharpType(column.DataTypeName, column.IsPrimaryKey), " ", column.Name, " { get; set; }");
                result.AppendLine();
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        public string GenerateCollection(string name)
        {
            var result = new StringBuilder();

            result.AppendLine("using System.Collections.Generic;");
            result.AppendLine("using System.Data;");
            result.AppendLine("using System.Data.SqlClient;");
            result.AppendLine("using BLL.Services.", name, ".Listing;");
            result.AppendLine();
            result.AppendLine("namespace BLL.Services.", name);
            result.AppendLine("{");
            result.AppendLine(1, "public class ", name, "Collection");
            result.AppendLine(1, "{");
            result.AppendLine(2, "private readonly string _connectionString;");
            result.AppendLine();
            result.AppendLine(2, "public ", name, "Collection(string connectionString)");
            result.AppendLine(2, "{");
            result.AppendLine(3, "_connectionString = connectionString;");
            result.AppendLine(2, "}");
            result.AppendLine();

            if (_isCreate)
            {
                result.AppendLine(2, "public void Create(", name, " model)");
                result.AppendLine(2, "{");
                result.AppendLine(3, "using (var conn = new SqlConnection(_connectionString))");
                result.AppendLine(3, "{");
                result.AppendLine(4, "conn.Open();");
                result.Append(4, "var cmd = StoredProcedures.Authors.Insert(conn");
                foreach (var column in _columns)
                {
                    if (!column.IsIdentity && !column.IsPrimaryKey)
                    {
                        result.Append(", model.", column.Name);
                    }
                }
                result.AppendLine(");");
                result.AppendLine(4, "cmd.ExecuteNonQuery();");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
                result.AppendLine();
            }

            if (_isValidate)
            {
                result.Append(2, "public bool Validate(");

                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (!isAdded)
                        isAdded = true;
                    else
                        result.Append(", ");
                    result.Append(GetCSharpType(column.DataTypeName, column.IsPrimaryKey), " ", GetWithLowerFirstChar(column.Name));
                }
                result.AppendLine(")");

                result.AppendLine(2, "{");
                result.AppendLine(3, "var result = false;");
                result.AppendLine(3, "using (var conn = new SqlConnection(_connectionString))");
                result.AppendLine(3, "{");
                result.AppendLine(4, "conn.Open();");
                result.Append(4, "var cmd = StoredProcedures.", name, ".Validate(conn");
                foreach (var column in _columns)
                {
                    result.Append(", ", GetWithLowerFirstChar(column.Name));
                }
                result.AppendLine(");");
                result.AppendLine();
                result.AppendLine(4, "var isValidParameter = new SqlParameter(\"@valid\", SqlDbType.Bit, 4) { Direction = ParameterDirection.ReturnValue };");
                result.AppendLine(4, "cmd.Parameters.Add(isValidParameter);");
                result.AppendLine();
                result.AppendLine(4, "cmd.ExecuteNonQuery();");
                result.AppendLine(4, "result = (int)isValidParameter.Value == 1;");
                result.AppendLine(3, "}");
                result.AppendLine(3, "return result;");
                result.AppendLine(2, "}");
                result.AppendLine();
            }

            if (_isDelete)
            {
                result.Append(2, "public void Delete(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsPrimaryKey), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "using (var conn = new SqlConnection(_connectionString))");
                result.AppendLine(3, "{");
                result.AppendLine(4, "conn.Open();");
                result.Append(4, "var cmd = StoredProcedures.", name, ".Delete(conn");
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        result.Append(", ", GetCSharpType(column.DataTypeName, column.IsPrimaryKey), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(");");
                result.AppendLine(4, "cmd.ExecuteNonQuery();");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
                result.AppendLine();
            }

            if (_isListing)
            {
                result.AppendLine(2, "public ", name, "Listing GetListing", name, "(");
                result.AppendLine(3, "int page, int size, string column, int direction, AuthorFilter filter)");
                result.AppendLine(2, "{");
                result.AppendLine(3, "if (filter == null)");
                result.AppendLine(4, "filter = new ", name, "Filter();");
                result.AppendLine(3, "using (var conn = new SqlConnection(_connectionString))");
                result.AppendLine(3, "{");
                result.AppendLine(4, "conn.Open();");
                result.AppendLine(4, "var cmd = StoredProcedures.", name, ".GetListing(");
                result.Append(5, "conn, page, size, column, direction");
                foreach (var column in _columns)
                {
                    if (!column.IsIdentity && !column.IsPrimaryKey)
                    {
                        result.Append(", filter.", column.Name);
                    }
                }
                result.AppendLine(");");
                result.AppendLine();
                result.AppendLine(4, "var totalCount = new SqlParameter(\"@TotalCount\", SqlDbType.Int, 4)");
                result.AppendLine(5, "{ Direction = ParameterDirection.ReturnValue };");
                result.AppendLine(4, "cmd.Parameters.Add(totalCount);");
                result.AppendLine();
                result.AppendLine(4, "var result = new ", name, "Listing");
                result.AppendLine(5, "{");
                result.AppendLine(6, "Page = page,");
                result.AppendLine(6, "Size = size,");
                result.AppendLine(6, "Direction = direction,");
                result.AppendLine(6, "Column = column,");
                result.AppendLine(6, "Filter = filter");
                result.AppendLine(5, "};");
                result.AppendLine();
                result.AppendLine(4, "using (var reader = cmd.ExecuteReader())");
                result.AppendLine(4, "{");
                result.AppendLine(5, "while (reader.Read())");
                result.AppendLine(5, "{");
                result.AppendLine(6, "result.Authors.Add(GetAuthor(reader));");
                result.AppendLine(5, "}");
                result.AppendLine(4, "}");
                result.AppendLine(4, "result.TotalPages = (int)totalCount.Value % size == 0");
                result.AppendLine(5, "? (int)totalCount.Value / size");
                result.AppendLine(5, ": ((int)totalCount.Value / size) + 1;");
                result.AppendLine(4, "return result;");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
                result.AppendLine();
            }

            if (_isSave)
            {
                result.AppendLine(2, "public void Save(", name, " model)");
                result.AppendLine(2, "{");
                result.AppendLine(3, "using (var conn = new SqlConnection(_connectionString))");
                result.AppendLine(3, "{");
                result.AppendLine(4, "conn.Open();");
                result.Append(4, "var cmd = StoredProcedures.Authors.Update(conn");
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        result.Append(", model.", column.Name);
                    }
                }
                result.AppendLine(");");
                result.AppendLine(4, "cmd.ExecuteNonQuery();");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
                result.AppendLine();
            }

            if (_isSelectById)
            {
                result.Append(2, "public ", name, " SelectById(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsPrimaryKey), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, name, " result = null;");
                result.AppendLine(3, "using (var conn = new SqlConnection(_connectionString))");
                result.AppendLine(3, "{");
                result.AppendLine(4, "conn.Open();");
                result.Append(4, "var cmd = StoredProcedures.", name, ".SelectById(conn");
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        result.Append(", ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(");");
                result.AppendLine();
                result.AppendLine(4, "using (var reader = cmd.ExecuteReader())");
                result.AppendLine(4, "{");
                result.AppendLine(5, "while (reader.Read())");
                result.AppendLine(6, "result = Get", name, "(reader);");
                result.AppendLine(4, "}");
                result.AppendLine(3, "}");
                result.AppendLine(3, "return result;");
                result.AppendLine(2, "}");
                result.AppendLine();
            }

            result.AppendLine(2, "private static ", name, " Get", name, "(IDataRecord reader)");
            result.AppendLine(2, "{");
            result.AppendLine(3, "var item = new ", name, "();");

            int i = 0;
            foreach (var column in _columns)
            {
                result.AppendLine(3, GetReaderMethod(column, i));
                i++;
            }

            result.AppendLine(3, "return item;");
            result.AppendLine(2, "}");
            result.AppendLine(1, "}");
            result.AppendLine("}");

            return result.ToString();
        }

        public string GenerateServiceInterface(string name)
        {
            var result = new StringBuilder();

            result.AppendLine("using System.Collections.Generic;");
            if (_isListing)
            {
                result.AppendLine("using BLL.Services.", name, ".Listing;");
            }
            result.AppendLine();
            result.AppendLine("namespace BLL.Services.", name);
            result.AppendLine("{");
            result.AppendLine(1, "public interface I", name, "Service");
            result.AppendLine(1, "{");

            if (_isSelectById)
            {
                result.Append(2, name, " Get", name, "ById(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(");");
                result.AppendLine();
            }

            if (_isCreate)
            {
                result.AppendLine(2, "void Create(", name, " ", GetWithLowerFirstChar(name), ");");
                result.AppendLine();
            }

            if (_isSave)
            {
                result.AppendLine(2, "void Save(", name, " ", GetWithLowerFirstChar(name), ");");
                result.AppendLine();
            }

            if (_isDelete)
            {
                result.Append(2, "void Delete(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(");");
                result.AppendLine();
            }

            if (_isListing)
            {
                result.AppendLine(2, name, "Listing GetListing", name, "(int page, int size, string column, int direction, ", name, "Filter filter);");
                result.AppendLine();
            }

            if (_isValidate)
            {
                result.Append(2, "bool Validate", name, "(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (!isAdded)
                        isAdded = true;
                    else
                        result.Append(", ");
                    result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), " ", GetWithLowerFirstChar(column.Name));
                }
                result.AppendLine(");");
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        public string GenerateService(string name)
        {
            var result = new StringBuilder();

            result.AppendLine("using System.Collections.Generic;");
            if (_isListing)
            {
                result.AppendLine("using BLL.Services.", name, ".Listing;");
            }
            result.AppendLine();
            result.AppendLine("namespace BLL.Services.", name);
            result.AppendLine("{");
            result.AppendLine(1, "public class ", name, "Service : I", name, "Service");
            result.AppendLine(1, "{");
            result.AppendLine(2, "private readonly ", name, "Collection _", GetWithLowerFirstChar(name), "Collection;");
            result.AppendLine();
            result.AppendLine(2, "public ", name, "Service(", name, "Collection collection)");
            result.AppendLine(2, "{");
            result.AppendLine(3, "_", GetWithLowerFirstChar(name), "Collection = collection;");
            result.AppendLine(2, "}");

            if (_isSelectById)
            {
                result.AppendLine();
                result.Append(2, "public ", name, " Get", name, "ById(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.Append(4, "return _", GetWithLowerFirstChar(name), "Collection.SelectById(");
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(");");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (SqlException sqlException)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "throw new ServiceException(sqlException.Message);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isCreate)
            {
                result.AppendLine();
                result.AppendLine(2, "public void Create(", name, " ", GetWithLowerFirstChar(name), ")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.AppendLine(4, "_", GetWithLowerFirstChar(name), "Collection.Create(", GetWithLowerFirstChar(name), ");");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (SqlException sqlException)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "throw new ServiceException(sqlException.Message);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isSave)
            {
                result.AppendLine();
                result.AppendLine(2, "public void Save(", name, " ", GetWithLowerFirstChar(name), ")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.AppendLine(4, "_", GetWithLowerFirstChar(name), "Collection.Save(", GetWithLowerFirstChar(name), ");");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (SqlException sqlException)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "throw new ServiceException(sqlException.Message);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isDelete)
            {
                result.AppendLine();
                result.Append(2, "public void Delete(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), " ", GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.Append(4, "_", GetWithLowerFirstChar(name), "Collection.Delete(");
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetWithLowerFirstChar(column.Name));
                    }
                }
                result.AppendLine(");");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (SqlException sqlException)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "throw new ServiceException(sqlException.Message);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isSave)
            {
                result.AppendLine();
                result.AppendLine(2, "public ", name, "Listing GetListing", name, "(int page, int size, string column, int direction, ", name, "Filter filter);");
                result.AppendLine(2, "{");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.AppendLine(4, "_", GetWithLowerFirstChar(name), "Collection.GetListing", name, "(page, size, column, direction, filter);");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (SqlException sqlException)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "throw new ServiceException(sqlException.Message);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isValidate)
            {
                result.AppendLine();
                result.Append(2, "public bool Validate", name, "(");
                var isAdded = false;
                foreach (var column in _columns)
                {
                    if (!isAdded)
                        isAdded = true;
                    else
                        result.Append(", ");
                    result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), " ", GetWithLowerFirstChar(column.Name));
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");

                result.Append(4, "_", GetWithLowerFirstChar(name), "Collection.Validate(");
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (!isAdded)
                        isAdded = true;
                    else
                        result.Append(", ");
                    result.Append(GetWithLowerFirstChar(column.Name));
                }
                result.AppendLine(");");

                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (SqlException sqlException)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "throw new ServiceException(sqlException.Message);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        private static string GetReaderMethod(ColumnInformation column, int pos)
        {
            var csType = GetCSharpType(column.DataTypeName, column.IsNullable);
            if (column.IsNullable && csType != "string")
            {
                return string.Format(@"{0} {1};
            if ({0}.TryParse(reader[{3}] as string, out {1}))
                item.{2} = {1};
            else
                item.{2} = null;", csType, GetWithLowerFirstChar(column.Name), column.Name, pos);
            }
            else
            {
                string method;
                switch (csType)
                {
                    case "string":
                        method = "GetString";
                        break;
                    case "int":
                        method = "GetInt32";
                        break;
                    case "double":
                        method = "GetDouble";
                        break;
                    case "byte":
                        method = "GetByte";
                        break;
                    case "System.DateTime":
                        method = "GetDateTime";
                        break;
                    default:
                        return string.Format("item.{0} = reader[{1}];", column.Name, pos);
                }
                return string.Format("item.{0} = reader.{1}({2});", column.Name, method, pos);
            }
        }

        private static string GetWithLowerFirstChar(string name)
        {
            return char.ToLower(name[0]) + name.Substring(1);
        }

        private static string GetCSharpType(string sqlType, bool isNullable)
        {
            if (sqlType.StartsWith("nvarchar") || sqlType.StartsWith("string"))
            {
                return "string";
            }

            switch (sqlType)
            {
                case "int":
                    return isNullable ? "int?" : "int";
                case "float":
                    return isNullable ? "double?" : "double";
                case "tinyint":
                    return isNullable ? "byte?" : "byte";
                case "date":
                case "datetime":
                case "datetime2":
                    return isNullable ? "System.DateTime?" : "System.DateTime";
                case "binary":
                    return "object";
                default:
                    return "dynamic";
            }
        }
    }
}